home *** CD-ROM | disk | FTP | other *** search
- (*COMPUTE - by James da Silva November 12, 1985
-
-
- This simple program was written to illustrate the mechanics of expression
- evaluation using recursive-descent techniques. It's a poor man's resident
- calculator program. Well, ok, it isn't resident and it isn't exactly a
- calculator, but it was fun, and believe it or not, I've actually found
- myself using it once or twice.
-
- Compute calculates the value of the arithmetic expression passed to it on
- the command line, then prints this value on the screen.
-
-
- Here is the BNF syntax for compute:
-
- expression ::= term { add_operator term }.
-
- term ::= factor { mul_operator factor }.
-
- factor ::= terminal | '-' terminal.
-
- terminal ::= number_constant | '(' expression ')'.
-
-
- add_operator ::= '+' | '-'.
-
- mul_operator ::= '*' | '/'.
-
- number_constant ::= digit {digit} { '.' {digit} }.
-
-
- COMPUTE could be easily extended to handle:
-
- 1. general output of numbers (i.e. 3 instead of 3.000,
- 3.1415926 instead of 3.1416)
- 2. hex and binary numbers.
- 3. multi-line expressions.
- 4. multiple expressions, carry-over of result. (a calculator)
- 5. builtin functions.
-
- Anyone care to try one?
-
- *)
-
- PROGRAM compute;
-
- TYPE
-
- token_type = (number, unknown, lparen, { these are all the things }
- rparen, plus, minus, { that can appear in our }
- times, divide, endt); { expressions. Note the }
- { endt (end of text) and }
- { unknown tokens. }
-
- String128 = string [128];
-
- VAR
- token: token_type;
- number_value, expr_value: real;
-
- position, start_of_token: integer;
- line: string128 absolute cseg: $80; { the command line, in person }
-
-
- { USAGE - called when nothing appears on the command line.
- It prints a short message about the function of
- the program, then exits to DOS. }
-
- PROCEDURE usage;
- BEGIN
- writeln('Usage: compute expression');
- writeln;
- writeln(
- 'Where expression is an algebraic expression consisting of'
- );
- writeln('simple real constants (i.e. -3, 2.7), the operators'
- );
- writeln('+, -, *, /, and parenthesized expressions.');
- writeln;
- writeln('Examples:');
- writeln(' compute 2+2 - returns 4');
- writeln(' compute (4-3)*8 +2* -3 - returns 2');
- writeln(' compute 2 * sin(x)/y - error');
- writeln;
- halt;
- END;
-
- { ERROR - prints the expression, then an error message underneath.
- error then returns to the operating system. }
-
- PROCEDURE error(s: string128);
-
- VAR
- x: integer;
- BEGIN
- for x := 1 to length(line) - 1 do
- write(line[x]);
- writeln;
- for x := 1 to start_of_token - 1 do
- write(' ');
- writeln('^- ', s);
- halt;
- END;
-
- { GET_TOKEN - This is our 'lexical analyzer'. It recognizes the basic
- language elements (like number, or '+') in the input line. }
-
- PROCEDURE get_token;
-
-
- { GET_NUMBER - This function recongnizes one numerical constant on
- the line and returns the internal REAL value of that
- constant. }
-
- FUNCTION get_number: real;
-
- VAR
- s: string128;
- r: real;
- code: integer;
- BEGIN
- s := '';
- while line[position] in ['0'..'9'] do (* get {digit} *)
- begin
- s := s + line[position];
- position := succ(position);
- end;
-
- if line[position] = '.' then (* get {'.' {digit}} *)
- begin
- s := s + '.';
- position := succ(position);
- while line[position] in ['0'..'9'] do
- begin
- s := s + line[position];
- position := succ(position);
- end
- end;
-
- val(s, r, code); {convert to internal number format}
- if code <> 0 then {shouldn't get here, but just in case}
- begin
- start_of_token := start_of_token + code - 1;
- error('error in constant');
- end;
- get_number := r; { return number }
- END;
-
- BEGIN {get_token}
- while line[position] = ' ' do {get past white space}
- position := succ(position);
-
- start_of_token := position; {mark start of token (for ERROR) }
-
- if line[position] in ['0'..'9', '.'] then {a number?}
- begin
- number_value := get_number;
- token := number;
- end {not a number- look up in table.}
- else {NOTE: this is dependent on the enumeration}
- begin {order in token_type. Oh well! }
- token := token_type(pos(line[position], '()+-*/#') + 1);
- position := succ(position);
- end
- END;
-
- { EXPRESSION - This is the meat of the whole program. The function
- interpretes the BNF given above for expressions, calling
- TERM to do the dirty work. }
-
- FUNCTION expression: real;
-
- VAR
- operator: token_type;
- accumulator: real;
-
- { TERM - THIS is the meat of the program. this function interpretes
- the BNF for term, calling FACTOR to do the real work. }
-
- FUNCTION term: real;
-
- VAR
- operator: token_type;
- accumulator, division_temporary: real;
-
- { FACTOR - Whould you believe that THIS is the meat of the
- program? Hardly. It just checks for unary minus and
- passes the job to TERMINAL. }
-
- FUNCTION factor: real;
-
- { TERMINAL - Ah. Here's the meat of the program. It gets
- its value from GET_TOKEN's number_value. That
- was easy enough. }
-
- FUNCTION terminal: real;
- BEGIN
- case token of
-
- lparen:
- begin
- get_token;
- terminal := expression; {going in circles!}
- if token <> rparen then
- error(''')'' expected');
- end;
-
- number:
- terminal := number_value;
-
- else
- error('number or expression expected');
-
- end;
- get_token;
- END; {terminal}
-
- BEGIN {factor} (* factor ::= '-' terminal | terminal. *)
- if token = minus then
- begin
- get_token;
- factor := - terminal;
- end
- else
- factor := terminal;
- END; {factor}
-
- BEGIN {term} (* term ::= factor {['*' | '/'] factor}. *)
- accumulator := factor;
- while token in [times, divide] do
- begin
- operator := token;
- get_token;
- if operator = times then
- accumulator := accumulator * factor
- else
- begin
- division_temporary := factor;
- if division_temporary <> 0 then
- accumulator := accumulator / division_temporary
- else
- error('division by zero')
- end;
- end;
- term := accumulator
- END;
-
- BEGIN {expression} (* expression ::= term {['+' | '-'] term}. *)
- accumulator := term;
- while token in [plus, minus] do
- begin
- operator := token;
- get_token;
- if operator = plus then
- accumulator := accumulator + term
- else
- accumulator := accumulator - term;
- end;
- expression := accumulator
-
- END; {expression}
-
- BEGIN {main}
- textcolor(7);
- textbackground(0);
-
- if line = '' then
- Usage;
-
- line := line + '#'; { guaranteed end-of-line token, an old trick- }
- position := 1; { no we don't have to keep checking for eol }
- get_token;
-
- expr_value := expression; { here's the beef }
-
- if token <> endt then { check for garbage first }
- begin
- if token = unknown then
- error('I don''t understand this')
- else
- error('end of line expected');
- end;
-
- writeln('= ', expr_value: 10: 4);
-
- END. {main}